Serializing Objects Using the XmlSerializer

In addition to the SOAP and binary formatters, the System.Xml.dll assembly provides a third formatter, System.Xml.Serialization.XmlSerializer. You can use this formatter to persist the public state of a given object as pure XML, as opposed to XML data wrapped within a SOAP message. Working with this type is a bit different from working with the SoapFormatter or BinaryFormatter type. Consider the following code, which assumes you have imported the System.Xml.Serialization namespace:

static void SaveAsXmlFormat(object objGraph, string fileName)
{
    // Save object to a file named CarData.xml in XML format.
    XmlSerializer xmlFormat = new XmlSerializer(typeof(JamesBondCar));

    using(Stream fStream = new FileStream(fileName,
        FileMode.Create, FileAccess.Write, FileShare.None))
    {
        xmlFormat.Serialize(fStream, objGraph);
    }
    Console.WriteLine("=> Saved car in XML format!");
}

The key difference is that the XmlSerializer type requires you to specify type information that represents the class you want to serialize. If you were to look within the newly generated XML file (assuming you call this new method from within Main()), you would find the XML data shown here:

<?xml version="1.0"?>
<JamesBondCar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <theRadio>
        <hasTweeters>true</hasTweeters>
        <hasSubWoofers>false</hasSubWoofers>
        <stationPresets>
            <double>89.3</double>
            <double>105.1</double>
            <double>97.1</double>
        </stationPresets>
        <radioID>XF-552RR6</radioID>
    </theRadio>
    <isHatchBack>false</isHatchBack>
    <canFly>true</canFly>
    <canSubmerge>false</canSubmerge>
</JamesBondCar>

Note The XmlSerializer demands that all serialized types in the object graph support a default constructor (so be sure to add it back if you define custom constructors). If this is not the case, you will receive an InvalidOperationException at runtime.

Controlling the Generated XML Data

If you have a background in XML technologies, you know that it is often critical to ensure the data within an XML document conforms to a set of rules that establish the validity of the data. Understand that a valid XML document does not have anything to do with the syntactic well-being of the XML elements (e.g., all opening elements must have a closing element). Rather, valid documents conform to agreedupon formatting rules (e.g., field X must be expressed as an attribute and not a sub-element), which are typically defined by an XML schema or document-type definition (DTD) file.

By default, XmlSerializer serializes all public fields/properties as XML elements, rather than as XML attributes. If you wish to control how the XmlSerializer generates the resulting XML document, you can decorate types with any number of additional .NET attributes from the System.Xml.Serialization namespace. Table 20-12 documents some (but not all) of the .NET attributes that influence how XML data is encoded to a stream.

Table 20-12. Select Attributes of the System.Xml.Serialization Namespace

.NET Attribute Meaning in Life
[XmlAttribute] You can use this .NET attribute on a public field or property in a class to tell XmlSerializer to serialize the data as an XML attribute (rather than as a subelement).
[XmlElement] The field or property will be serialized as an XML element named as you so choose.
[XmlEnum] This attribute provides the element name of an enumeration member.
[XmlRoot] This attribute controls how the root element will be constructed (namespace and element name).
[XmlText] The property or field will be serialized as XML text (i.e. the content between the start tag and the end tag of the root element).
[XmlType] This attribute provides the name and namespace of the XML type.

This simple example illustrates how the field data of JamesBondCar is currently persisted as XML:

<?xml version="1.0" encoding="utf-8"?>
<JamesBondCar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
...
    <canFly>true</canFly>
    <canSubmerge>false</canSubmerge>
</JamesBondCar>

If you wish to specify a custom XML namespace that qualifies the JamesBondCar and encodes the canFly and canSubmerge values as XML attributes, you can do so by modifying the C# definition of JamesBondCar:

[XmlRoot(Namespace = "http://www.MyCompany.com")]
public class JamesBondCar : Car
{
    [XmlAttribute]
    public bool canFly;
    [XmlAttribute]
    public bool canSubmerge;
}

This yields the following XML document (note the opening <JamesBondCar> element):

<?xml version="1.0"""?>
<JamesBondCar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    canFly="true" canSubmerge="false"
    xmlns="http://www.MyCompany.com">
...
</JamesBondCar>

Of course, you can use many other attributes to control how the XmlSerializer generates the resulting XML document. For full details, look up the System.Xml.Serialization namespace in the .NET Framework 4.0 SDK documentation.